package com.fengye.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.util.Collection;
import java.util.Map;

/**
 * @author fengyexjtu@126.com
 * @date 2022年04月11日 8:41 PM
 */
@Slf4j
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 所有 url 的请求都要启用过滤器链 - 配置服务的安全功能
     * <p>
     * <p>
     * /api/test 只要认证就可以访问
     * /api/** 剩下的/api 下的接口必须拥有 ROLE_ADMIN 权限才能访问
     * <p>
     * 启用 httpBasic 认证,form 认证禁用
     *
     * @param http HttpSecurity
     * @author fengyexjtu@126.com
     * @date 2022/4/11 8:49 PM
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // formLogin
                .formLogin(form -> form.loginPage("/login")
                        .usernameParameter("username") // 表单用户名参数
                        .passwordParameter("password") // 表单密码参数
                        .successHandler(jsonAuthenticationSuccessHandler())
                        .failureHandler(jsonAuthenticationFailureHandler())
                        .permitAll())

                // 权限控制
                .authorizeRequests(req -> {
                    req.mvcMatchers("/api/test").authenticated();
                    req.mvcMatchers("/api/**").hasAnyRole("ADMIN");
                    req.anyRequest().authenticated();
                })

                // httpBasic认证
                .httpBasic(Customizer.withDefaults())

                // csrf token
                .csrf(Customizer.withDefaults())

                // rememberMe
                .rememberMe(rememberMe ->
                        rememberMe
                                .tokenValiditySeconds(30 * 24 * 3600)
                                .rememberMeParameter("remember-meq")
                                .rememberMeCookieName("somethingToRememberMeCookie")
                )

                // logout 注销
                .logout(logout ->
                        logout.logoutUrl("/perform_logout")
                                .logoutSuccessHandler(jsonLogoutSuccessHandler())
                )
        ;
    }

    /**
     * 退出登录逻辑自定义
     *
     * @return org.springframework.security.web.authentication.logout.LogoutSuccessHandler
     * @author fengyexjtu@126.com
     * @date 2022/4/13 8:12 AM
     */
    private LogoutSuccessHandler jsonLogoutSuccessHandler() {
        return (request, response, authentication) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            val data = Map.of(
                    "title", "注销成功",
                    "details", authentication.getName()
            );
            SecurityContextHolder.clearContext();
            response.getWriter().println(objectMapper.writeValueAsString(data));
        };
    }

    /**
     * 认证失败处理
     *
     * @return org.springframework.security.web.authentication.AuthenticationFailureHandler
     * @author fengyexjtu@126.com
     * @date 2022/4/13 8:13 AM
     */
    private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
        return (request, response, exception) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            val errData = Map.of(
                    "title", "认证失败",
                    "details", exception.getMessage()
            );
            response.getWriter().println(objectMapper.writeValueAsString(errData));
        };
    }


    /*
     * 参见 {@link SimpleUrlAuthenticationSuccessHandler#onAuthenticationSuccess }
     * 函数式接口 一个接口只有一个方法需要去实现.可以直接 lambda 函数去实现,而不用重新定义一个类去实现该接口
     * @author fengyexjtu@126.com
     * @date 2022/4/12 11:19 PM
     * @return org.springframework.security.web.authentication.AuthenticationSuccessHandler
     */
    private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
        return (request, response, authentication) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            response.setStatus(HttpStatus.OK.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding("UTF-8");
            response.getWriter().println(objectMapper.writeValueAsString(authentication));
            log.debug("认证成功");
        };
    }

    /*
     * 处理 url 的请求不需要经过过滤器 - 用于忽略静态资源
     * @author fengyexjtu@126.com
     * @date 2022/4/12 10:00 AM
     * @param web
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/open/**");
        // 常用位置忽略安全检查
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user")
                .password(passwordEncoder().encode("12345678"))
                .roles("ADMIN", "USER");
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}